package com.group.itms.routes.provider;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;

import com.group.itms.routes.ThisApplication;
import com.group.itms.routes.data.Advertisement;
import com.group.itms.routes.data.Topic;
import com.group.itms.routes.json.JSONParser;
import com.group.itms.routes.json.ServiceInfo;
import com.group.itms.routes.stub.BObjectOptionStub;
import com.group.itms.routes.stub.BObjectStub;
import com.group.itms.routes.stub.BObjectTopicStub;
import com.group.itms.routes.stub.ObjectGpsStub;
import com.group.itms.routes.stub.ObjectMapStub;
import com.group.itms.routes.stub.OptionStub;

public class DataProviderImpl extends DataProvider {

	private static final String TAG = "DataProviderImpl";
	private static final String LAST_MODIFIED = "last_modified";
	private static final boolean DEBUG = false;
	
	private JSONParser parser;
	private ServiceInfo serviceInfo;
	private boolean ignoreErrors;
	private int errors;

	private JSONObject jsonObj;

	public DataProviderImpl(JSONParser parser, ServiceInfo serviceInfo) {
		this.parser = parser;
		this.serviceInfo = serviceInfo;
	}

	private String buildRequestString(String lastModified) {
		return serviceInfo.getProtocol() + "://" + serviceInfo.getHost() + ":"
				+ serviceInfo.getPort() + "/" + serviceInfo.getResourceUrl()
				+ "?date=" + lastModified;
	}

	private JSONObject parseJson() throws JSONException, IOException {
		String requestUrl = buildRequestString(URLEncoder.encode(
				lastModified(), "UTF-8"));
		return parser.parse(requestUrl);
	}

	public String lastModified() {
		SharedPreferences prefs = ThisApplication.getInstance()
				.getPreferences();
		return prefs.getString(LAST_MODIFIED, "0000-00-00 00:00:00");
	}

	private void parseJsonIfNeeded() throws ProviderException {
		if (jsonObj == null) {
			try {
				jsonObj = parseJson();
			} catch (JSONException e) {
				throw new ProviderException(e, ProviderException.JSON_EXCEPTION);
			} catch (IOException e) {
				throw new ProviderException(e, ProviderException.IO_EXCEPTION);
			}
		}
	}

	private JSONArray getJSONArray(String name) throws ProviderException {
		if (jsonObj == null) {
			throw new IllegalStateException("JSONObject is not parsed yet");
		}
		try {
			return jsonObj.getJSONArray(name);
		} catch (JSONException e) {
			throw new ProviderException(
					"Unable to extract JSONArray with name: " + name
							+ " from JSONObject " + jsonObj, e,
					ProviderException.JSON_EXCEPTION);
		}
	}

	private JSONObject getJSONObject(JSONArray array, int index)
			throws ProviderException {
		try {
			return array.getJSONObject(index);
		} catch (JSONException e) {
			throw new ProviderException(
					"Unable to extract JSONObject at index: " + index
							+ " from JSONArray: " + array, e,
					ProviderException.JSON_EXCEPTION);
		}
	}

	private int getInt(JSONObject object, String name) throws ProviderException {
		try {
			return object.getInt(name);
		} catch (JSONException e) {
			throw new ProviderException("Unable to get int with name: " + name
					+ " from JSONObject: " + object, e,
					ProviderException.JSON_EXCEPTION);
		}
	}

	private String getString(JSONObject object, String name)
			throws ProviderException {
		try {
			return object.getString(name);
		} catch (JSONException e) {
			throw new ProviderException("Unable to get string with name: "
					+ name + " from JSONObject: " + object, e,
					ProviderException.JSON_EXCEPTION);
		}
	}

	private float getFloat(JSONObject object, String name)
			throws ProviderException {
		try {
			return (float) object.getDouble(name);
		} catch (JSONException e) {
			throw new ProviderException("Unable to get float with name:"
					+ name + " from JSONObject: " + object, e,
					ProviderException.JSON_EXCEPTION);
		}
	}

	@Override
	public List<BObjectStub> getObjects() throws ProviderException {
		parseJsonIfNeeded();
		JSONArray objects = getJSONArray("object");
		int size = objects.length();
		List<BObjectStub> stubObjects = new ArrayList<BObjectStub>(size);
		for (int i = 0; i < size; i++) {
			BObjectStub stubObj;
			try {
				stubObj = parseObjectStub(getJSONObject(objects, i));
				if (stubObj != null) {
					stubObjects.add(stubObj);
				}
			} catch (ProviderException ex) {
				if (!ignoreErrors) {
					throw ex;
				} else {
					errors++;
				}
			}
		}
		return stubObjects;
	}
	
	private BObjectStub parseObjectStub(JSONObject obj) throws ProviderException {
		if (getInt(obj, "lang") == 1) return null;
		BObjectStub stubObj = new BObjectStub();
		boolean isActive = getInt(obj, "active") == 1;
		stubObj.setId(getInt(obj, "id"));
		stubObj.setActive(isActive);
		if (isActive) {
			stubObj.setName(getString(obj, "name"));
			stubObj.setObjInd(getString(obj, "obj_ind"));
			stubObj.setParentId(getInt(obj, "parent_id"));
			stubObj.setText(getString(obj, "text"));
			stubObj.setTypeId(getInt(obj, "object_type"));
			stubObj.setColor(getString(obj, "color"));
		}
		return stubObj;
	}

	@Override
	public List<Topic> getTopics() throws ProviderException {
		parseJsonIfNeeded();
		JSONArray topics = getJSONArray("tematika");
		int size = topics.length();
		List<Topic> stubtopics = new ArrayList<Topic>(size);
		for (int i = 0; i < size; i++) {
			Topic tpc;
			try {
				tpc = parseTopicStub(getJSONObject(topics, i));
				if (tpc != null) {
					stubtopics.add(tpc);
				}
			} catch (ProviderException ex) {
				if (!ignoreErrors) {
					throw ex;
				} else {
					errors++;
				}
			}
		}
		return stubtopics;
	}
	
	private Topic parseTopicStub(JSONObject topic) throws ProviderException {
		if (getInt(topic, "lang") == 1) return null;
		Topic tpc = new Topic();
		boolean isActive = getInt(topic, "active") == 1;
		tpc.setId(getInt(topic, "id"));
		tpc.setActive(isActive);
		if (isActive) {
			tpc.setName(getString(topic, "name"));
		}
		return tpc;
	}

	@Override
	public List<OptionStub> getOptions() throws ProviderException {
		parseJsonIfNeeded();
		JSONArray options = getJSONArray("field");
		int size = options.length();
		List<OptionStub> stubOptions = new ArrayList<OptionStub>(size);
		for (int i = 0; i < size; i++) {
			OptionStub stubOpt;
			try {
				stubOpt = parseOptionStub(getJSONObject(options, i));
				if (stubOpt != null) {
					stubOptions.add(stubOpt);
				}
			} catch (ProviderException ex) {
				if (!ignoreErrors) {
					throw ex;
				} else {
					errors++;
				}
			}
		}
		return stubOptions;
	}
	
	private OptionStub parseOptionStub(JSONObject opt) throws ProviderException {
		if (getInt(opt, "lang") == 1) return null;
		OptionStub stubOpt = new OptionStub();
		boolean isActive = getInt(opt, "active") == 1;
		stubOpt.setId(getInt(opt, "id"));
		stubOpt.setActive(isActive);
		if (isActive) {
			stubOpt.setName(getString(opt, "name"));
			stubOpt.setObjectTypeId(getInt(opt, "object_type"));
		}
		return stubOpt;
	}

	@Override
	public List<BObjectOptionStub> getObjectOptions() throws ProviderException {
		parseJsonIfNeeded();
		JSONArray objectOptions = getJSONArray("object_field");
		int size = objectOptions.length();
		List<BObjectOptionStub> stubObjectOptions = new ArrayList<BObjectOptionStub>(size);
		for (int i = 0; i < size; i++) {
			BObjectOptionStub stubObjOpt;
			try {
				stubObjOpt = parseObjectOptionStub(getJSONObject(objectOptions, i));
				if (stubObjOpt != null) {
					stubObjectOptions.add(stubObjOpt);
				}
			} catch (ProviderException ex) {
				if (!ignoreErrors) {
					throw ex;
				} else {
					errors++;
				}
			}
		}
		return stubObjectOptions;
	}

	private BObjectOptionStub parseObjectOptionStub(JSONObject objOpt) throws ProviderException {
		if (getInt(objOpt, "lang") == 1) return null;
		BObjectOptionStub stubObjOpt = new BObjectOptionStub();
		boolean isActive = getInt(objOpt, "active") == 1;
		stubObjOpt.setId(getInt(objOpt, "id"));
		stubObjOpt.setActive(isActive);
		if (isActive) {
			stubObjOpt.setObjectId(getInt(objOpt, "object_id"));
			stubObjOpt.setOptionId(getInt(objOpt, "field_id"));
			stubObjOpt.setText(getString(objOpt, "text"));
		}
		return stubObjOpt;
	}
	
	@Override
	public List<BObjectTopicStub> getObjectTopics() throws ProviderException {
		parseJsonIfNeeded();
		JSONArray objectOptions = getJSONArray("object_tematika");
		int size = objectOptions.length();
		List<BObjectTopicStub> stubObjectOptions = new ArrayList<BObjectTopicStub>(size);
		for (int i = 0; i < size; i++) {
			BObjectTopicStub stubObjTpc;
			try {
				stubObjTpc = parseObjectTopicStub(getJSONObject(objectOptions, i));
				if (stubObjTpc != null) {
					stubObjectOptions.add(stubObjTpc);
				}
			} catch (ProviderException ex) {
				if (!ignoreErrors) {
					throw ex;
				} else {
					errors++;
				}
			}
		}
		return stubObjectOptions;
	}
	
	private BObjectTopicStub parseObjectTopicStub(JSONObject objTpc) throws ProviderException {
		BObjectTopicStub stubObjTpc = new BObjectTopicStub();
		boolean isActive = getInt(objTpc, "active") == 1;
		stubObjTpc.setId(getInt(objTpc, "id"));
		stubObjTpc.setActive(isActive);
		if (isActive) {
			stubObjTpc.setObjectId(getInt(objTpc, "object_id"));
			stubObjTpc.setTopicId(getInt(objTpc, "tematika_id"));
		}
		return stubObjTpc;
	}

	@Override
	public List<ObjectGpsStub> getGpsObjects() throws ProviderException {
		parseJsonIfNeeded();
		JSONArray objectGpses = getJSONArray("obj_gps");
		int size = objectGpses.length();
		List<ObjectGpsStub> stubObjectTypes = new ArrayList<ObjectGpsStub>(size);
		for (int i = 0; i < size; i++) {
			ObjectGpsStub stubObjGps;
			try {
				stubObjGps = parseObjectGpsStub(getJSONObject(objectGpses, i));
				if (stubObjGps != null) {
					stubObjectTypes.add(stubObjGps);
				}
			} catch (ProviderException ex) {
				if (!ignoreErrors) {
					throw ex;
				} else {
					errors++;
				}
			}
		}
		return stubObjectTypes;
	}
	
	private ObjectGpsStub parseObjectGpsStub(JSONObject objGps) throws ProviderException {
		ObjectGpsStub stubObjGps = new ObjectGpsStub();
		boolean isActive = getInt(objGps, "active") == 1;
		stubObjGps.setId(getInt(objGps, "id"));
		stubObjGps.setActive(isActive);
		if (isActive) {
			stubObjGps.setObjectId(getInt(objGps, "obj_id"));
			stubObjGps.setX(getFloat(objGps, "gps1"));
			stubObjGps.setY(getFloat(objGps, "gps2"));
			stubObjGps.setZ(getFloat(objGps, "gps3"));
		}
		return stubObjGps;
	}

	@Override
	public List<ObjectMapStub> getObjectMaps() throws ProviderException {
		if (DEBUG) {
			return new ArrayList<ObjectMapStub>();
		}
		parseJsonIfNeeded();
		JSONArray objectMaps = getJSONArray("object_map");
		int size = objectMaps.length();
		List<ObjectMapStub> stubObjectMaps = new ArrayList<ObjectMapStub>(size);
		for (int i = 0; i < size; i++) {
			ObjectMapStub stubObjMap;
			try {
				stubObjMap = parseObjectMapStub(getJSONObject(objectMaps, i));
				if (stubObjMap != null) {
					stubObjectMaps.add(stubObjMap);
				}
			} catch (ProviderException ex) {
				if (!ignoreErrors) {
					throw ex;
				} else {
					errors++;
				}
			}
		}
		return stubObjectMaps;
	}
	
	private ObjectMapStub parseObjectMapStub(JSONObject objMap) throws ProviderException {
		ObjectMapStub stubObjMap = new ObjectMapStub();
		boolean isActive = getInt(objMap, "active") == 1;
		stubObjMap.setId(getInt(objMap, "id"));
		stubObjMap.setActive(isActive);
		if (isActive) {
			stubObjMap.setMapUrl(getString(objMap, "map"));
			stubObjMap.setObject(getInt(objMap, "object_id"));
			stubObjMap.setX1(getFloat(objMap, "x1"));
			stubObjMap.setY1(getFloat(objMap, "y1"));
			stubObjMap.setX2(getFloat(objMap, "x2"));
			stubObjMap.setY2(getFloat(objMap, "y2"));
			stubObjMap.setX3(getFloat(objMap, "x3"));
			stubObjMap.setY3(getFloat(objMap, "y3"));
			stubObjMap.setX4(getFloat(objMap, "x4"));
			stubObjMap.setY4(getFloat(objMap, "y4"));
			stubObjMap.startDownloading();
		}
		return stubObjMap;
	}

	@Override
	public List<Advertisement> getAdvertisements() throws ProviderException {
		parseJsonIfNeeded();
		JSONArray advertisements = getJSONArray("adv");
		int size = advertisements.length();
		List<Advertisement> advList = new ArrayList<Advertisement>(size);
		for (int i = 0; i < size; i++) {
			JSONObject advJson = getJSONObject(advertisements, i);
			Advertisement adv = new Advertisement();
			boolean isActive = getInt(advJson, "active") == 1;
			adv.setActive(isActive);
			adv.setId(getInt(advJson, "id"));
			if (isActive) {
				// TODO download photo (icon)
				adv.setImage(getString(advJson, "photo"));
				adv.setUrl(getString(advJson, "url"));
			}
			advList.add(adv);
		}
		return advList;
	}
	
	@Override
	public void markCompleted() {
		if (jsonObj == null) {
			return;
		}
		String serverTimeStamp;
		try {
			serverTimeStamp = getString(jsonObj, "date");
		} catch (ProviderException e) {
			Log.e(TAG, "Unable to complete data download transaction", e);
			return;
		}
		SharedPreferences prefs = ThisApplication.getInstance().getPreferences();
		Editor editor = prefs.edit();
		editor.putString(LAST_MODIFIED, serverTimeStamp);
		editor.commit();
	}

	@Override
	public void setIgnoreErrors(boolean ignoreErrors) {
		this.ignoreErrors = ignoreErrors;
	}

	@Override
	public int getErrors() {
		return errors;
	}

}
